package com.sky.demo.retry;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;


@Aspect
@Component
@Slf4j
@ConditionalOnProperty()
public class RetryAspect {

    @Pointcut("@annotation(com.sky.demo.retry.Retry)")
    public void pointcut(){

    }

    @Around("pointcut()")
    public Object doRetry(ProceedingJoinPoint joinPoint) throws Throwable{
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        try{
            return joinPoint.proceed();
        }catch (Throwable e){
            Retry retry = AnnotationUtils.getAnnotation(method, Retry.class);
            if(retry != null){
                int retryTimes = retry.retryTimes();
                long executeInterval = retry.executeInterval();
                Class<? extends Exception>[] retryFor = retry.retryFor();
                Class<? extends Throwable>[] noRetryFor = retry.noRetryFor();

                if(retryTimes < 1
                    || isInstance(noRetryFor, e)
                        || !isInstance(retryFor, e)
                ){
                    throw e;
                }

                int currentRetryTime = 1;
                while(true){
                    currentRetryTime++;
                    try{
                        if(executeInterval > 0){
                            ThreadUtil.safeSleep(executeInterval);
                        }
                        return joinPoint.proceed();
                    }catch (Throwable th){
                        if(currentRetryTime > retryTimes){
                            throw th;
                        }
                    }
                }
            }
            throw e;
        }
    }

    private boolean isInstance(Class<? extends Throwable>[] noRetryFor, Throwable e) {
        if(ArrayUtil.isEmpty(noRetryFor) || e == null){
            return false;
        }
        for(Class<? extends Throwable> exp : noRetryFor){
            if(exp.isInstance(e)){
                return true;
            }
        }
        return false;
    }
}
